﻿/*
ChangeTracking (a library for POCO change tracking).

Copyright (C) 2014 Dmitry Merzagitovich Arslanov

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace ChangeTracking
{
    /// <summary>
    /// Creates graph of object instances. For graph creation it uses
    /// reflection and object instance as single source vertex. Public instance
    /// properties with both getter and setter are interpreted as graph arches.
    /// </summary>
    public sealed class ObjectGraphBuilder
    {
        private Digraph.Builder<object, Property> graphBuilder;

        /// <summary>
        /// Object instance that is graph source vertex.
        /// </summary>
        public object Object { get; private set; }

        /// <summary>
        /// Graph of object instances.
        /// </summary>
        public Digraph.Set<object, Property> Graph
        {
            get
            {
                return graphBuilder.Digraph;
            }
        }

        /// <summary>
        /// Creates new instance of ObjectGraphBuilder.
        /// </summary>
        /// <param name="obj">
        /// Object instance that is graph source vertex.
        /// </param>
        public ObjectGraphBuilder(object obj)
        {
            Object = obj;
        }

        /// <summary>
        /// Creates graph of object instances from source vertex.
        /// </summary>
        /// <returns>Created graph.</returns>
        public Digraph.Set<object, Property> Build()
        {
            graphBuilder = new Digraph.Builder<object, Property>();
            Digraph.Vertex<object, Property> rootNode =
                graphBuilder.NewVertex(Object);
            if (Object != null) Build(rootNode);
            return graphBuilder.Digraph;
        }

        private void Build(Digraph.Vertex<object, Property> rootNode)
        {
            Type rootContentType = rootNode.Content.GetType();
            if (rootContentType.Name == "String") return;
            PropertyInfo[] propertyInfos = rootContentType.GetProperties(
                BindingFlags.Instance |
                BindingFlags.Public |
                BindingFlags.GetProperty |
                BindingFlags.SetProperty);
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.Name == "Item") continue;
                object propertyValue = propertyInfo.GetValue(
                    rootNode.Content, null);
                if (propertyValue is IList)
                {
                    IList list = propertyValue as IList;
                    int index = 0;
                    foreach (object item in list)
                    {
                        Digraph.Vertex<object, Property> node =
                            graphBuilder.Digraph.GetVertex(
                                obj => object.Equals(obj.Content, item));
                        if (node == null)
                        {
                            node = graphBuilder.NewVertex(item);
                            graphBuilder.NewArc(rootNode, node,
                                new ListProperty(propertyInfo, list, index));
                            if (item != null) Build(node);
                        }
                        else
                        {
                            graphBuilder.NewArc(rootNode, node,
                                new ListProperty(propertyInfo, list, index));
                        }
                        index++;
                    }
                }
                else
                {
                    Digraph.Vertex<object, Property> node =
                        graphBuilder.Digraph.GetVertex(
                            obj => object.Equals(obj.Content, propertyValue));
                    if (node == null)
                    {
                        node = graphBuilder.NewVertex(propertyValue);
                        graphBuilder.NewArc(rootNode,
                                            node,
                                            new SingleProperty(propertyInfo));
                        if (propertyValue != null) Build(node);
                    }
                    else
                    {
                        graphBuilder.NewArc(rootNode,
                                            node,
                                            new SingleProperty(propertyInfo));
                    }
                }
            }
        }
    }
}
